/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  ArkFile,
  ArkMethod,
  ArkNamespace
} from 'arkanalyzer';
import { Rule } from '../../model/Rule';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from '../BaseChecker';
import { Defects, IssueReport } from '../../model/Defects';
import {
  ClassMatcher,
  FieldMatcher,
  FileMatcher,
  MatcherCallback,
  MatcherTypes,
  MethodMatcher,
} from '../../matcher/Matchers';
import { CheckerStorage } from '../../Index';
import { RuleListUtil } from '../../utils/common/DefectsList';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoNameSpaceCheck');
export type Options = [
  {
    allowDeclarations?: boolean;
    allowDefinitionFiles?: boolean;
  },
];

export class NoNameSpaceCheck implements BaseChecker {
  issues: IssueReport[] = [];
  private defaultOptions: Options = [
    {
      allowDeclarations: false,
      allowDefinitionFiles: true,
    },
  ];
  public rule: Rule;
  public defects: Defects[] = [];

  public metaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: 'docs/no-namespace-check.md',
    description: 'ES2015 module syntax is preferred over namespaces.',
  };

  private fileMatcher: FileMatcher = {
    matcherType: MatcherTypes.FILE,
  };

  public registerMatchers(): MatcherCallback[] {
    const matchFileCb: MatcherCallback = {
      matcher: this.fileMatcher,
      callback: this.check
    }
    return [matchFileCb];
  }

  public check = (target: ArkFile) => {
    if (target) {
      const filePath = target.getFilePath();
      if (filePath.endsWith(".ts")) {
        const namespaces = target.getAllNamespacesUnderThisFile()
        for (let i = 0; i < namespaces.length; i++) {
          const codes = namespaces[i].getCodes();
          const lineCols = namespaces[i].getLineColPairs();
          for (let j = 0; j < codes.length; j++) {
            if (this.getOption()[0].allowDeclarations) {
              let str = codes[j].replace(/\s+/g, '');
              if (str.slice(0, 7) == 'declare' || str.slice(0, 11) == 'exportdeclare') {
                return;
              }
              this.isAllowDeclarations(namespaces[i], lineCols[j][0], lineCols[j][1], codes[j]);
            } else {
              this.isNotAllowDeclarations(namespaces[i], lineCols[j][0], lineCols[j][1], codes[j]);
            }
          }

        }
      }
    }
  };

  private isAllowDeclarations(namespaces: ArkNamespace, line: number, col: number, codeString: string) {
    const code = codeString.split('\n')[0].replace('\r', '');
    if (/^(?=.*module)(?!.*declare).*$/.test(code) || /^(?=.*namespace)(?!.*declare).*$/.test(code)) {
      this.addIssueReport(namespaces, line, col, codeString);
    }
  }

  private isNotAllowDeclarations(namespaces: ArkNamespace, line: number, col: number, codeString: string) {
    const code = codeString.split('\n')[0].replace('\r', '');
    if (/^(?=.*module)(?!.*declare).*$/.test(code) || /^(?=.*namespace)(?!.*declare).*$/.test(code) ||
      /declare namespace/.test(code) || /declare module\s+([^'"\s].*?[^'"\s])/.test(code)) {
      this.addIssueReport(namespaces, line, col, codeString);
    }
  }

  private addIssueReport(namespaces: ArkNamespace, line: number, col: number, codeString: string) {
    const severity = this.rule.alert ?? this.metaData.severity;
    const warnInfo = this.getLineAndColumn(namespaces, line, col, codeString);
    const defect = new Defects(warnInfo.line, warnInfo.startCol ?? 1, warnInfo.startCol ?? 1, this.metaData.description, severity, this.rule.ruleId, namespaces.getDeclaringArkFile().getFilePath(), this.metaData.ruleDocPath, true, false, false);
    this.issues.push(new IssueReport(defect, undefined));
    RuleListUtil.push(defect);
  }

  private getLineAndColumn(namespaces: ArkNamespace, line: number, col: number, codeString: string) {
    let startCol = this.getStartColum(namespaces, col, codeString);
    let filePath = namespaces.getDeclaringArkFile().getFilePath();
    return { line, startCol, filePath };
  }

  private getStartColum(namespaces: ArkNamespace, col: number, codeString: string) {
    let colNo = col;
    const code = codeString.split('\n')[0].replace('\r', '');
    if (/^(?=.*module)(?!.*declare).*$/.test(code)) {
      return (
        colNo + code.indexOf('module')
      );
    } else if (/^(?=.*namespace)(?!.*declare).*$/.test(code)) {
      return (
        colNo + code.indexOf('namespace')
      );
    } else if (/declare/.test(code) || /declare module/.test(code) || /declare namespace/.test(code)) {
      return (
        colNo + code.indexOf('declare')
      );
    }
  }

  private getOption(): Options {
    let option: Options;
    if (this.rule && this.rule.option) {
      option = this.rule.option as Options;
      if (option[0]) {
        if (!option[0]?.allowDeclarations) {
          option[0].allowDeclarations = false;
        }
        if (!option[0]?.allowDefinitionFiles) {
          option[0].allowDefinitionFiles = true;
        }
        return option;
      }
    }
    return this.defaultOptions;
  }
}